﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Timers;
using Microsoft.ServiceBus;
using System.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using nwrole.plugin;
using Timer = System.Timers.Timer;

namespace nwrole.lib {
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true)]
    public class Supervisor : ISupervisorContract {
        public static string Identifier = "nwrole.SupervisorService";
        //public static string ServiceDisplayName = "nwrole Supervisor";
        //public static string ApplicationDirectory = "nwrole";

        private IOrchestratorChannel orchestratorChannel;
        public int SupervisorPort;

        private Timer masterConnectionTimer = new Timer();

        private List<ServiceData> ServiceDatas = new List<ServiceData>();

        private SupervisorData supervisorData;

        private IServiceProvider serviceProvider;
        private string pluginProviderClassName;

        private bool useOrchestrator = true;
        private string orchestratorSBServiceNamespace;
        private string orchestratorSBServicePath;
        private string orchestratorSBIssuerName;
        private string orchestratorSBIssuerSecret;

        private bool orchestratorConnectionErrorReported = false;

        private string smtpHost;
        private int smtpPort = 0;
        private string responsibleEmail;

        // LogThread
        /*private static int maxLogQueueLength = 30;
        private ConcurrentQueue<SupervisorLogMessage> logQueue = new ConcurrentQueue<SupervisorLogMessage>();
        private AutoResetEvent logQueueNotifier = new AutoResetEvent(false);
        private Thread logThread;*/

        public Supervisor(IServiceProvider serviceProvider, string pluginProviderClassName, int supervisorPort) {

            this.serviceProvider = serviceProvider;
            this.pluginProviderClassName = pluginProviderClassName;
            SupervisorPort = supervisorPort;

            /*logThread = new Thread(ProcessMessages);
            logThread.Start();*/

            masterConnectionTimer.Elapsed += OnMasterConnectionTimer;
            masterConnectionTimer.Interval = 60 * 1000;

            supervisorData = new SupervisorData();

            serviceProvider.Start();

            serviceProvider.SupervisorSettingsChangedEvent += OnSupervisorSettingsChanged;
            serviceProvider.ServiceSettingsChangedEvent += OnServiceSettingsChanged;

            LogSupervisor(LogLevel.Info, "Supervisor loading settings", null);

            PropertyList plist = serviceProvider.LoadSupervisorSettings();
            if (plist != null)
            {
                int port = 0;
                if (int.TryParse(plist["SupervisorPort"] as string, out port))
                {
                    SupervisorPort = port;
                }

                useOrchestrator = (bool)plist["UseOrchestrator"];
            }

            LogSupervisor(LogLevel.Info, string.Format("Supervisor loading settings Done (useOrchestrator:{0})", useOrchestrator), null);
        }

        #region Supervisor
        public Result Start() {

            LogSupervisor(LogLevel.Info, string.Format("Supervisor Start (useOrchestrator:{0})", useOrchestrator), null);
            EventLogger.LogInformation("Supervisor", "Supervisor starting (port " + SupervisorPort + ")");
            try {
                ServiceDatas.Clear();

                LogSupervisor(LogLevel.Info, "Supervisor loading settings", null);

                PropertyList plist = serviceProvider.LoadSupervisorSettings();
                if (plist != null) {
                    supervisorData.DisplayName = plist["Name"] as string;
                    supervisorData.Id = new Guid(plist["Id"] as string);
                    orchestratorSBServiceNamespace = plist["OrchestratorSBServiceNamespace"] as string;
                    orchestratorSBServicePath = plist["OrchestratorSBServicePath"] as string;
                    orchestratorSBIssuerName = plist["OrchestratorSBIssuerName"] as string;
                    orchestratorSBIssuerSecret = plist["OrchestratorSBIssuerSecret"] as string;
                    smtpHost = plist["SmtpHost"] as string;
                    int tempSmtpPort = 0;
                    if (int.TryParse(plist["SmtpPort"] as string, out tempSmtpPort))
                        SupervisorPort = tempSmtpPort;
                    responsibleEmail = plist["ResponsibleEmail"] as string;
                } else {
                    plist = new PropertyList();
                    plist["SupervisorPort"] = "" + SupervisorPort;
                    plist["Name"] = Environment.MachineName;
                    //plist["Test"] = new TimeSpan(3746736434);
                    plist["Id"] = Guid.NewGuid();
                    plist["UseOrchestrator"] = useOrchestrator;
                    plist["OrchestratorSBServiceNamespace"] = "";
                    plist["OrchestratorSBServicePath"] = "";
                    plist["OrchestratorSBIssuerName"] = "";
                    plist["OrchestratorSBIssuerSecret"] = "";
                    plist["SmtpHost"] = "";
                    plist["SmtpPort"] = 0;
                    plist["ResponsibleEmail"] = "";
                    serviceProvider.SaveSupervisorSettings(plist);
                }

                try {
                    if(orchestratorChannel == null)
                        ConnectToOrchestrator();
                } catch {

                }

                LogSupervisor(LogLevel.Info, "Supervisor loading settings Done", null);

                /*string appDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                string supervisorConfig = Path.Combine(appDir, "supervisor-config.xml");
                if(File.Exists(supervisorConfig)) {
                    PropertyList plist = new PropertyList(supervisorConfig);
                    supervisorData.Name = plist["Name"] as string;
                    supervisorData.Id = new Guid(plist["Id"] as string);
                } else {
                    PropertyList plist = new PropertyList();
                    plist["Name"] = Environment.MachineName;
                    plist["Id"] = Guid.NewGuid();
                    plist.Save(supervisorConfig);
                }*/

                //ServiceDatas = new List<ServiceData>(serviceProvider.GetServices());

                string[] serviceNames = serviceProvider.GetServiceNames();

                LogSupervisor(LogLevel.Info, "Supervisor num services: " + serviceNames.Length, null);

                foreach(string serviceName in serviceNames) {
                    ServiceData serviceData = null;
                    PropertyList serviceSettings = serviceProvider.LoadServiceSettings(serviceName);
                    if(serviceSettings != null) {
                        serviceData = StorageUtil.ServiceDataFromDictionary((IDictionary)serviceSettings.DictionaryForKey("Service"));
                    }

                    if (serviceData == null) {
                        serviceData = new ServiceData();
                        serviceData.Name = serviceName;
                        serviceData.Id = Guid.NewGuid();
                        serviceData.Enabled = true;
                        
                        PropertyList newServiceSettings = new PropertyList();
                        newServiceSettings["Service"] = StorageUtil.ServiceDataToDictionary(serviceData);
                        serviceProvider.SaveServiceSettings(newServiceSettings, serviceName);
                    }

                    serviceData.ManagementPort = NextFreeManagementPort();

                    ServiceDatas.Add(serviceData);
                }

                foreach (var serviceData in ServiceDatas)
                {
                    StartService(serviceData);
                }

                LogSupervisor(LogLevel.Info, "Supervisor started", null);

            } catch (Exception exception) {
                LogSupervisor(LogLevel.Error, "Supervisor.Start exception", exception);
                EventLogger.LogException("Supervisor.Start", exception);
                return Result.UnknownError;
            }
            EventLogger.LogInformation("Supervisor", "Supervisor started");
            return Result.Success;
        }

        public Result Stop() {
            EventLogger.LogInformation("Supervisor", "Supervisor stopping");
            foreach (var serviceData in ServiceDatas) {
                try
                {
                    StopService(serviceData);
                } 
                catch (Exception exception) 
                {
                    LogSupervisor(LogLevel.Error, "Supervisor.Stop exception", exception);
                    EventLogger.LogException("Supervisor.Stop", exception);
                    return Result.UnknownError;
                }
            }
            
            EventLogger.LogInformation("Supervisor", "Supervisor stopped");
            return Result.Success;
        }
        #endregion

        public void OnSupervisorSettingsChanged(object sender, EventArgs e)
        {
            LogSupervisor(LogLevel.Debug, "Supervisor.OnSupervisorSettingsChanged", null);

            PropertyList plist = serviceProvider.LoadSupervisorSettings();

            bool needReRegister = false;
            if(supervisorData.DisplayName != plist["Name"] as string)
            {
                needReRegister = true;
                supervisorData.DisplayName = plist["Name"] as string;
            }

            if(supervisorData.Id != new Guid(plist["Id"] as string))
            {
                needReRegister = true;
                supervisorData.Id = new Guid(plist["Id"] as string);
            }

            if(useOrchestrator != (bool)plist["UseOrchestrator"])
            {
                useOrchestrator = (bool)plist["UseOrchestrator"];
                if (useOrchestrator)
                {
                    needReRegister = false;
                    ConnectToOrchestrator();
                }
                else
                    DisconnectFromOrchestrator();
            }

            if(needReRegister)
            {
                if (orchestratorChannel != null)
                {
                    try
                    {
                        orchestratorChannel.RegisterSupervisor(supervisorData.Id, supervisorData.DisplayName);
                    }
                    catch
                    {
                        OrchestratorChannelDisconnected();
                    }
                }
            }
        }

        public void OnServiceSettingsChanged(object sender, EventArgs e)
        {
            LogSupervisor(LogLevel.Debug, "Supervisor.OnServiceSettingsChanged", null);

            NwroleSettingsChangedEventArgs settingsChanged = (NwroleSettingsChangedEventArgs)e;

            string serviceName = settingsChanged.Name;

            // TODO: do something
        }

        #region ISupervisorContract

        public int GetNumServices() {
            LogSupervisor(LogLevel.Debug, "Supervisor.GetNumServies", null);
            try
            {
                return ServiceDatas.Count;
            }
            catch(Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.GetNumServices exception", e);
                throw;
            }
        }

        /*public Guid GetIdentifier() {
            return supervisorId;
        }*/

        public SupervisorData GetSupervisorData() {
            LogSupervisor(LogLevel.Debug, "Supervisor.GetSupervisorData", null);
            try
            {
                return supervisorData;
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.GetSupervisorData exception", e);
                throw;
            }
        }

        public ServiceData[] GetServices() {
            LogSupervisor(LogLevel.Debug, "Supervisor.GetServices", null);
            try
            {
                return ServiceDatas.ToArray();
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.GetServices exception", e);
                throw;
            }
        }

        public Result RestartService(Guid serviceId)
        {
            LogSupervisor(LogLevel.Debug, "Supervisor.RestartService", null);
            try
            {
                ServiceData serviceData = ServiceDatas.SingleOrDefault(s => s.Id == serviceId);
                if (serviceData == null)
                    return Result.ServiceNotFound;

                /*using (var service = GetServiceChannel(serviceData.ManagementPort, serviceId))
                {
                    service.Channel.Stop();
                    return service.Channel.Start();
                }*/

                Result result = StopService(serviceData);
                if (result != Result.Success)
                    return result;

                return StartService(serviceData);
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.RestartService exception", e);
                throw;
            }
        }

        public Result StopService(Guid serviceId)
        {
            LogSupervisor(LogLevel.Debug, "Supervisor.StopService", null);
            try
            {
                ServiceData serviceData = ServiceDatas.SingleOrDefault(s => s.Id == serviceId);
                if (serviceData == null)
                    return Result.ServiceNotFound;

                return StopService(serviceData);
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.StopService exception", e);
                throw;
            }
        }

        public Result StartService(Guid serviceId)
        {
            LogSupervisor(LogLevel.Debug, "Supervisor.StartService", null);
            try
            {
                ServiceData serviceData = ServiceDatas.SingleOrDefault(s => s.Id == serviceId);
                if (serviceData == null)
                    return Result.ServiceNotFound;

                return StartService(serviceData);
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.StartService exception", e);
                throw;
            }
        }

        public Result RestartPlugin(Guid serviceId, Guid pluginId)
        {
            LogSupervisor(LogLevel.Debug, "Supervisor.RestartPlugin", null);
            try
            {
                ServiceData serviceData = ServiceDatas.SingleOrDefault(s => s.Id == serviceId);
                if (serviceData == null)
                    return Result.ServiceNotFound;

                using (var service = GetServiceChannel(serviceData.ManagementPort, serviceId))
                {
                    return service.Channel.RestartPlugin(pluginId);
                }
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.RestartPlugin exception", e);
                throw;
            }
        }

        public Result StopPlugin(Guid serviceId, Guid pluginId)
        {
            LogSupervisor(LogLevel.Debug, "Supervisor.StopPlugin", null);
            try
            {
                ServiceData serviceData = ServiceDatas.SingleOrDefault(s => s.Id == serviceId);
                if (serviceData == null)
                    return Result.ServiceNotFound;

                using (var service = GetServiceChannel(serviceData.ManagementPort, serviceId))
                {
                    return service.Channel.StopPlugin(pluginId);
                }
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.StopPlugin exception", e);
                throw;
            }
        }

        public Result StartPlugin(Guid serviceId, Guid pluginId)
        {
            LogSupervisor(LogLevel.Debug, "Supervisor.StartPlugin", null);
            try
            {
                ServiceData serviceData = ServiceDatas.SingleOrDefault(s => s.Id == serviceId);
                if (serviceData == null)
                    return Result.ServiceNotFound;

                using (var service = GetServiceChannel(serviceData.ManagementPort, serviceId))
                {
                    return service.Channel.StartPlugin(pluginId);
                }
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.StartPlugin exception", e);
                throw;
            }
        }

        public PluginData[] GetServicePluginsData(Guid serviceId)
        {
            LogSupervisor(LogLevel.Debug, "Supervisor.GetServicePluginsData", null);
            try
            {
                ServiceData serviceData = ServiceDatas.SingleOrDefault(s => s.Id == serviceId);
                if (serviceData == null)
                    return null;

                using (var service = GetServiceChannel(serviceData.ManagementPort, serviceId))
                {
                    return service.Channel.GetPluginDatas();
                }
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.GetServicePluginsData exception", e);
                throw;
            }
        }

        public PluginState? GetServicePluginState(Guid serviceId, Guid pluginId)
        {
            LogSupervisor(LogLevel.Debug, "Supervisor.GetServicePluginState", null);
            try
            {
                ServiceData serviceData = ServiceDatas.SingleOrDefault(s => s.Id == serviceId);
                if (serviceData == null)
                    return null;

                using (var service = GetServiceChannel(serviceData.ManagementPort, serviceId))
                {
                    return service.Channel.GetPluginState(pluginId);
                }
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.GetServicePluginState exception", e);
                throw;
            }
        }
        
        private void RegisterService(ServiceData serviceData) {
            LogSupervisor(LogLevel.Debug, "Supervisor.RegisterService", null);
            try
            {
                if (orchestratorChannel != null) {
                    try {
                        orchestratorChannel.RegisterService(supervisorData.Id, serviceData.Id, serviceData.Name);
                    } catch {
                        OrchestratorChannelDisconnected();
                    }
                }
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.RegisterService exception", e);
                throw;
            }
        }

        public void LogSupervisor(LogLevel level, string message, Exception ex) {
            Log(NwroleObjectType.Supervisor, supervisorData.Id, level, message, ex);
        }

        /*public void LogService(Guid serviceId, LogLevel level, string msg, Exception ex) {
            WriteLog("Supervisor.LogService: " + msg + ": " + ex);
            if (orchestratorChannel != null) {
                try {
                    orchestratorChannel.LogService(serviceId, level, msg, ex);
                } catch (Exception e) {
                    OrchestratorChannelDisconnected();
                }
            }
        }

        public void LogPlugin(Guid pluginId, LogLevel level, string msg, Exception ex) {
            WriteLog("Supervisor.LogPlugin: " + msg + ": " + ex);
            if (orchestratorChannel != null) {
                try {
                    orchestratorChannel.LogPlugin(pluginId, level, msg, ex);
                } catch (Exception e) {
                    OrchestratorChannelDisconnected();
                }
            }
        }*/

        public void Log(NwroleObjectType objectType, Guid objectId, LogLevel level, string message, Exception ex) {
            if (ex != null)
            {
                WriteLog("Supervisor.Log: " + message +  " Exception: " + ex.ToString());
            }
            else
            {
                WriteLog("Supervisor.Log: " + message);
            }

            // LogThread
            /*logQueue.Enqueue(new SupervisorLogMessage(){ObjectType = objectType, ObjectId = objectId, Level = level, Message = message, Ex = ex});
            logQueueNotifier.Set();*/

            if (orchestratorChannel != null) {
                try {
                    orchestratorChannel.Log(objectType, objectId, level, message, ex);
                } catch {
                    OrchestratorChannelDisconnected();
                }
            }
        }

        // LogThread
        /*private void ProcessLogMessages() {
            while (true) {
                if (logQueue.IsEmpty)
                    logQueueNotifier.WaitOne();

                if (orchestratorChannel == null)
                    Thread.Sleep(15000);
                else {
                    SupervisorLogMessage logMessage;
                    if (logQueue.TryDequeue(out logMessage))
                        InternalSendLogMessage(logMessage);
                }
            }
        }

        private void InternalSendLogMessage(SupervisorLogMessage logMessage) {
            if (orchestratorChannel != null) {
                try {
                    orchestratorChannel.Log(logMessage.ObjectType, logMessage.ObjectId, logMessage.Level, logMessage.Message, logMessage.Ex);
                } catch (Exception e) {
                    OrchestratorChannelDisconnected();
                }
            }
        }*/

        private static void WriteLog(string content) {
          try {
            Trace.WriteLine(content);
            string logDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "log/Supervisor/");
            if (!System.IO.Directory.Exists(logDir))
              System.IO.Directory.CreateDirectory(logDir);

            DateTime now = DateTime.Now;

            System.IO.File.AppendAllText(logDir + "log " + now.ToString("yyyy-MM-dd") + ".txt", now.ToString("yyyy-MM-dd HH:mm:ss ") + content + "\r\n");
          } catch { }
        }

        public void RegisterPlugin(Guid serviceId, Guid pluginId, string displayName) {
            LogSupervisor(LogLevel.Info, "Supervisor.RegisterPlugin: " + pluginId + " displayName: " + displayName, null);
            if (orchestratorChannel != null) {
                try {
                    orchestratorChannel.RegisterPlugin(serviceId, pluginId, displayName);
                } catch {
                    OrchestratorChannelDisconnected();
                }
            }
        }

        #endregion

        private Result StopService(ServiceData serviceData)
        {
            LogSupervisor(LogLevel.Debug, "Supervisor.StopService", null);
            if (serviceData.Deployed)
            {
                /*if(!DeployUtil.StopService(serviceData.Id))
                    return Result.UnknownError;*/
                Result result = DeployUtil.RetractService(serviceData, true);
                if (result != Result.Success)
                    return result;
            }

            return Result.Success;
        }

        private Result StartService(ServiceData serviceData)
        {
            LogSupervisor(LogLevel.Debug, "Supervisor.StartService", null);
            RegisterService(serviceData);
            if (serviceData.Enabled)
            {
                /*if(!DeployUtil.StartService(serviceData.Id))
                    return Result.UnknownError;*/
                
                Result result = DeployUtil.DeployService(serviceData, true, SupervisorPort, pluginProviderClassName);
                if (result != Result.Success)
                    return result;
            }

            return Result.Success;
        }

        public void PulseService(Guid id, string serviceName, string message)
        {
            try
            {
                serviceProvider.PulseService(id, serviceName, message);
            }
            catch (Exception e)
            {
                string msg = string.Format("Error in constructor Supervisor.PulseService Id: {0} ServiceName: {1} Message: {2}", id, serviceName, message);
                LogSupervisor(LogLevel.Error, msg, e);
                throw;
            }
        }

        public void PulsePlugin(Guid id, string pluginName, string message)
        {
            try
            {
                serviceProvider.PulsePlugin(id, pluginName, message);
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.PulsePlugin exception", e);
                throw;
            }
        }

        public ServicePulse GetServicePulse(Guid serviceId)
        {
            try
            {
                return serviceProvider.GetServicePulse(serviceId);
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.GetServicePulse exception", e);
                throw;
            }
        }

        public PluginPulse GetPluginPulse(Guid pluginId)
        {
            try
            {
                return serviceProvider.GetPluginPulse(pluginId);
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.GetPluginPulse exception", e);
                throw;
            }
        }

        public void EmailResponsible(string subject, string message)
        {
            if (string.IsNullOrEmpty(smtpHost) || string.IsNullOrEmpty(responsibleEmail))
                return;

            try
            {
                MailAddress from = new MailAddress("nwrole@jarfallahus.se", "Järfällahus nwrole");
                MailAddress to = new MailAddress(responsibleEmail);

                using (MailMessage mail = new MailMessage(from, to))
                {
                    mail.Subject = subject;
                    mail.Body = message;
                    mail.IsBodyHtml = false;

                    if (smtpPort > 0)
                        new SmtpClient(smtpHost, smtpPort).Send(mail);
                    else
                        new SmtpClient(smtpHost).Send(mail);
                }   
            }
            catch(Exception e)
            {
                LogSupervisor(LogLevel.Debug, "Error, unable to send e-mail", e);
            }
        }

        public void ConnectToOrchestrator() {
            if(!useOrchestrator)
                return;

            try {
                ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.AutoDetect;

                LogSupervisor(LogLevel.Info, "Supervisor.ConnectToOrchestrator() namespace: " + orchestratorSBServiceNamespace + " servicePath: " + orchestratorSBServicePath, null);

                //Uri serviceUri = ServiceBusEnvironment.CreateServiceUri("sb", "nwrole", "nwrole/Orchestrator");
                Uri serviceUri = ServiceBusEnvironment.CreateServiceUri("sb", orchestratorSBServiceNamespace, orchestratorSBServicePath);

                //Console.WriteLine("Connecting to {0} address: {1}", DeployContext.Config.DeployName, serviceUri);

                TransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();
                /*sharedSecretServiceBusCredential.CredentialType = TransportClientCredentialType.SharedSecret;
                sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerName = "owner";
                sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerSecret = "Km+nny0pbfguRgaHjslR/IkWpUprTzgrJYZwvXiCJwU=";*/
                //sharedSecretServiceBusCredential.TokenProvider = TokenProvider.CreateSharedSecretTokenProvider("owner", "Km+nny0pbfguRgaHjslR/IkWpUprTzgrJYZwvXiCJwU=");
                sharedSecretServiceBusCredential.TokenProvider = TokenProvider.CreateSharedSecretTokenProvider(orchestratorSBIssuerName, orchestratorSBIssuerSecret);
                //ChannelFactory<IOrchestratorChannel> channelFactory = new ChannelFactory<IOrchestratorChannel>();
                DuplexChannelFactory<IOrchestratorChannel> channelFactory = new DuplexChannelFactory<IOrchestratorChannel>(new OrchestratorClient(this));
                channelFactory.Endpoint.Address = new EndpointAddress(serviceUri);
                //channelFactory.Endpoint.Binding = new NetTcpRelayBinding();
                channelFactory.Endpoint.Binding = new NetTcpRelayBinding(EndToEndSecurityMode.Transport, RelayClientAuthenticationType.RelayAccessToken);
                channelFactory.Endpoint.Binding.OpenTimeout = new TimeSpan(0,0,40);
                channelFactory.Endpoint.Contract.ContractType = typeof(IOrchestratorChannel);

                channelFactory.Endpoint.Behaviors.Add(sharedSecretServiceBusCredential);

                orchestratorChannel = channelFactory.CreateChannel();
                orchestratorChannel.Open();

                orchestratorChannel.Faulted += orchestratorChannel_Faulted;
                orchestratorChannel.Closed += orchestratorChannel_Closed;

                orchestratorChannel.RegisterSupervisor(supervisorData.Id, supervisorData.DisplayName);

                LogSupervisor(LogLevel.Info, "Supervisor: Connected to Orchestrator", null);
                orchestratorConnectionErrorReported = false;
            }
            catch(Exception e)
            {
                orchestratorChannel = null;
                if (!orchestratorConnectionErrorReported)
                {
                    orchestratorConnectionErrorReported = true;
                    LogSupervisor(LogLevel.Info, "Supervisor: unable to connect to Orchestrator", e);
                    //if (e.InnerException != null)
                    //    WriteLog("Inner exception: " + e.InnerException);
                }
                
            }

            if (orchestratorChannel == null) {
                
                masterConnectionTimer.Start();
            }
        }

        public void DisconnectFromOrchestrator()
        {
            if (orchestratorChannel != null)
            {
                orchestratorChannel.Close();
                orchestratorChannel = null;
            }
        }

        void orchestratorChannel_Closed(object sender, EventArgs e) {
            Console.WriteLine("Supervisor: Orchestrator channel closed");
            OrchestratorChannelDisconnected();
        }

        void orchestratorChannel_Faulted(object sender, EventArgs e) {
            Console.WriteLine("Supervisor: Orchestrator channel faulted");
            OrchestratorChannelDisconnected();
        }

        private void OrchestratorChannelDisconnected() {
            orchestratorChannel = null;
            if(useOrchestrator)
                masterConnectionTimer.Start();
        }

        private void OnMasterConnectionTimer(object source, ElapsedEventArgs e) {
            masterConnectionTimer.Stop();
            ConnectToOrchestrator();
        }

        #region Service management
        public static bool IsWinServiceInstalled
        {
            get {
                ServiceController ctrl = new ServiceController(Identifier);
                try {
                    ServiceControllerStatus status = ctrl.Status;
                    return true;
                } catch (Exception) { return false; } finally { ctrl.Dispose(); }
            }
        }

        public static bool IsWinServiceRunning
        {
            get {
                if (!IsWinServiceInstalled)
                    return false;
                ServiceController ctrl = new ServiceController(Identifier);
                try { return ctrl.Status == ServiceControllerStatus.Running; } finally { if (ctrl != null) ctrl.Dispose(); }
            }
        }

        public static void StartWinService()
        {
            if (!IsWinServiceInstalled)
                return;
            ServiceController ctrl = new ServiceController(Identifier);
            try {
                ctrl.Start();
                ctrl.WaitForStatus(ServiceControllerStatus.Running);
            } finally { if (ctrl != null) ctrl.Dispose(); }
        }

        public static void StopWinService() {
            if (!IsWinServiceRunning)
                return;
            ServiceController ctrl = new ServiceController(Identifier);
            try {
                ctrl.Stop();
                ctrl.WaitForStatus(ServiceControllerStatus.Stopped);
            } finally { if (ctrl != null) ctrl.Dispose(); }
        }

        public static void InstallWinService()
        {
            DeployUtil.InstallSupervisor(Identifier);
        }
        
        /*public static void InstallSupervisor(string username, string password) {
            string rootFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), ApplicationDirectory);

            UpdateSupervisor();

            ServiceUtil.InstallService(Path.Combine(rootFolder, Configuration.ApplicationExe) + " supervisor", Identifier, ServiceDisplayName, username, password);

            EventLogger.LogInformation("SupervisorService installed");
        }

        public static void UpdateSupervisor() {
            string rootFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), ApplicationDirectory);

            if (!Directory.Exists(rootFolder))
                Directory.CreateDirectory(rootFolder);

            // Metod 1
            //foreach (AssemblyName assemblyName in Assembly.GetExecutingAssembly().GetReferencedAssemblies())
            //{
            //    Assembly assembly = Assembly.Load(assemblyName);
            //    if (!assembly.GlobalAssemblyCache)
            //    {
            //        string name = Path.GetFileName(assembly.Location);
            //        File.Copy(assembly.Location, Path.Combine(rootFolder, name), true);
            //    }
            //}

            // Metod 2
            // copy the needed dll's
            foreach (string name in new string[] { "nwrole.lib.dll", "nwrole.lib2.dll", "nwrole.plugin.dll", "Microsoft.ServiceBus.dll", "nwrole.service.exe", "ICSharpCode.SharpZipLib.dll" }) {
                string source = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), name);
                File.Copy(source, Path.Combine(rootFolder, name), true);
            }
        }

        public static void UninstallSupervisor() {
            string rootFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), ApplicationDirectory);

            ServiceUtil.UninstallService(Identifier);

            if (Directory.Exists(rootFolder))
                Directory.Delete(rootFolder, true);

            EventLogger.LogInformation("SupervisorService uninstalled");
        }*/
        #endregion

        public int NextFreeManagementPort() {
            // int port = 63000;
            int port = SupervisorPort + 1;
            bool success = true;
            do {
                port++;
                success = true;
                foreach (var serviceData in ServiceDatas) {
                    if (serviceData.ManagementPort == port) {
                        success = false;
                        break;
                    }
                }
            } while (!success);
            return port;
        }

        public Dictionary<string, object> GetPluginProviderSettings() {
            LogSupervisor(LogLevel.Debug, "Supervisor.GetPluginProviderSettings", null);
            try
            {
                return serviceProvider.GetPluginProviderSettings();
            }
            catch (Exception e)
            {
                LogSupervisor(LogLevel.Error, "Supervisor.GetPluginProviderSettings exception", e);
                throw;
            }
        }

        private TcpChannel<IServiceContract> GetServiceChannel(int servicePort, Guid serviceId)
        {
            TcpChannel<IServiceContract> channel = new TcpChannel<IServiceContract>("localhost:" + servicePort + "/" + serviceId.ToString("N"));
            return channel;
        }
    }

    class SupervisorLogMessage {
        public NwroleObjectType ObjectType { get; set; }
        public Guid ObjectId { get; set; }
        public LogLevel Level { get; set; }
        public string Message { get; set; }
        public Exception Ex { get; set; }
    }

    [Serializable]
    public class ServicePulse
    {
        public Guid ServiceId;
        public string Message;
        public DateTime Date;
    }

    [Serializable]
    public class PluginPulse
    {
        public Guid PluginId;
        public string Message;
        public DateTime Date;
    }
}
